/*
 * Primary app code
 */

// Global app settings

APP_DATA = null;
APP_EVENTS= {};
APP_UTIL = {
    parseBoolOption: function(val, def) {
        if (val == null) {
            if (def === undefined)
                return true;
            else
                return def == true;
        }

        var str = (val + '').toLowerCase();
        return str == '' || str == 'true' || str == '1' || str == 'yes' || str == 'on';
    },

    isValidBoolOption: function(val) {
        if (val == null)
            return true;

        var t = (val + '').toLowerCase();
        return val == '' || t == '1' || t == 'true' || t == 'on' || t == 'yes' || t == '0' || t == 'false' || t == 'off' || t == 'no';
    },

    parseOptionStrings: function(val, dict, validateCallback) {
        dict = dict || {};
        var lines = replace_all(val || '', '\r', '\n').split('\n');
        for(var i in lines) {
            var line = lines[i].trim();
            if (line != '' && line[0] != '#') {
                if (line.indexOf('--') == 0) {
                    line = line.substr(2);
                }

                var eqpos = line.indexOf('=');
                var key = line;
                var value = true;
                if (eqpos > 0) {
                    key = line.substr(0, eqpos).trim();
                    value = line.substr(eqpos + 1).trim();
                    if (value == '')
                        value = true;
                }

                if (validateCallback)
                    if (!validateCallback(dict, key, value))
                        return null;

                dict['--' + key] = value;
            }
        }

        return dict;
    },

    fill_form: function(form, data, map, extra) {
        map = map || {};
        data = data || {};

        for(var k in data) {
            var key = k;
            var m = map[key];
            var v = data[k];

            if (m !== false) {
                if (m && typeof(m) == typeof(''))
                    key = m;

                if (m && typeof(m) == typeof(function() {})) {
                    m(data, key, v, extra);
                } else {
                    var n = form.find('#' + key);
                    if (n.attr('type') == 'checkbox') {
                        n.attr('checked', APP_UTIL.parseBoolOption(v));
                    } else {
                        n.val(v);
                    }

                    n.change();
                }
            }
        }
    },

    read_form: function(form, map, values, extra) {
        values = values || {};
        map = map || {};

        form.find('select').each(function(i, e) {
            var key = e.id;
            var m = map[e.id];

            if (m !== false) {
                if (m && typeof(m) == typeof(function() {})) {
                    m(values, key, e, extra);
                } else {
                    if (m && typeof(m) == typeof(''))
                        key = m;

                    values[key] = $(e).val();
                }
            }
        });

        form.find('textarea').each(function(i, e) {
            var key = e.id;
            var m = map[e.id];

            if (m !== false) {
                if (m && typeof(m) == typeof(function() {})) {
                    m(values, key, e, extra);
                } else {
                    if (m && typeof(m) == typeof(''))
                        key = m;

                    values[key] = $(e).val();
                }
            }
        });

        form.find('input').each(function(i, e) {
            var key = e.id;
            var m = map[e.id];

            if (m !== false) {
                if (m && typeof(m) == typeof(function() {})) {
                    m(values, key, e, extra);
                } else {
                    if (m && typeof(m) == typeof(''))
                        key = m;

                if (e.type == 'checkbox')
                    values[key] = $(e).is(':checked');
                else
                    values[key] = $(e).val();
                }
            }
        });

        return values;
    }
};

$(document).ready(function() {

    // Flag as loaded in case we have plugins
    APP_DATA = {
        plugins: {
            backend: {},
            compression: {},
            encryption: {},
            primary: {}

        }
    };

    var PRIVATE_DATA = {};

    $('#main-appname').text(APP_CONFIG.branded_name);

    window.document.title = APP_CONFIG.branded_name;

    if ((APP_CONFIG.branded_subtitle || '').length > 0) {
        var subdiv = $('<div id="main-appname-subtitle">');
        subdiv.text(APP_CONFIG.branded_subtitle);
        $('#main-appname').append(subdiv);
        $('#main-appname').addClass('has-subtitle');
    }

    $('.button').button();

    $('#main-list-container > div.main-backup-entry').remove();
    $('#loading-dialog').dialog({modal: true}).show();

    jQuery.timeago.settings.allowFuture = true;
    jQuery.timeago.settings.allowPast = true;

    $.noty.defaults.layout = 'bottomCenter';

    // Register a global function for password strength
    $.passwordStrength = function(password, callback) {
        if (callback == null)
            return;

        var onUpdate = function(res) {
            try { callback(res); }
            catch (e) { }
        };

        try { onUpdate(zxcvbn(password)); }
        catch (e) {
            // Not loaded, try this:
            $.getScript('/scripts/zxcvbn.js', function() {
                try {
                    onUpdate(zxcvbn(password));
                }
                catch (e) {
                    onUpdate(null);
                }
            });
        }
    };

    var formatSizes = ['TB', 'GB', 'MB', 'KB'];
    $.formatSizeString = function(val) {
        val = parseInt(val || 0);
        var max = formatSizes.length;
        for(var i = 0; i < formatSizes.length; i++) {
            var m = Math.pow(1024, max - i);
            if (val > m)
                return (val / m).toFixed(2) + ' ' + formatSizes[i];
        }

        return val + ' ' + bytes;
    };

    pwz = function(i, c) {
        i += '';
        while(i.length < c)
            i = '0' + i;
        return i;
    }

    $.toDisplayDateAndTime = function(dt) {
        return pwz(dt.getFullYear(), 4) + '-' + pwz(dt.getMonth() + 1, 2) + '-' + pwz(dt.getDate(), 2) + ' ' + pwz(dt.getHours(), 2) + ':' + pwz(dt.getMinutes(), 2);
    };

    $.parseDate = function(dt) {
        if (typeof(dt) == typeof('')) {
            var msec = Date.parse(dt);
            if (isNaN(msec)) {
                if (dt.length == 16 && dt[8] == 'T' && dt[15] == 'Z') {
                    dt = dt.substr(0, 4) + '-' + dt.substr(4, 2) + '-' + dt.substr(6, 2) + 'T' +
                              dt.substr(9, 2) + ':' + dt.substr(11, 2) + ':' + dt.substr(13, 2) + 'Z';
                }
                return new Date(dt);
            } else {
                return new Date(msec);
            }
        }
        else
            return new Date(dt);
    };

    var serverWithCallback = function(data, callback, errorhandler, refreshMethod) {
        if (typeof(data) == typeof(''))
            data = { action: data };

        var method = 'GET';
        if (data.HTTP_METHOD) {
            method = data.HTTP_METHOD;
            delete data.HTTP_METHOD;
        }

        $.ajax({
            url: APP_CONFIG.server_url,
            type: method,
            dataType: 'json',
            data: data
        })
        .done(function(data) {
            if (refreshMethod)
                refreshMethod(data, true, null);

            if (callback != null)
                callback(data, true, null);
        })
        .fail(function(data, status) {
            var msg = data.statusText;
            if (data && data.responseJSON && data.responseJSON.Message)
                msg = data.responseJSON.Message;

            if (refreshMethod)
                refreshMethod(data, false, msg);

            if (errorhandler)
                errorhandler(data, false, msg);
        });
    };

    PRIVATE_DATA.server_state = {
        polling: false,
        eventId: -1,
        state: null,
        failed: false,
        dataEventId: -1,

        retryTimer: null,
        pauseUpdateTimer: null,
        activeTask: null,
        scheduled: []
    };

    PRIVATE_DATA.progress_state_text = {
        'Backup_Begin': 'Starting ...',
        'Backup_PreBackupVerify': 'Verifying backend data ...',
        'Backup_PostBackupTest': 'Verifying remote data ...',
        'Backup_PreviousBackupFinalize': 'Completing previous backup ...',
        'Backup_ProcessingFiles': null,
        'Backup_Finalize': 'Completing backup ...',
        'Backup_WaitForUpload': 'Waiting for upload ...',
        'Backup_Delete': 'Deleting unwanted files ...',
        'Backup_Compact': 'Compacting remote data ...',
        'Backup_VerificationUpload': 'Uploadind verification file ...',
        'Backup_PostBackupVerify': 'Verifying backend data ...',
        'Backup_Complete': 'Finished!',
        'Restore_Begin': 'Starting ...',
        'Restore_RecreateDatabase': 'Rebuilding local database ...',
        'Restore_PreRestoreVerify': 'Verifying remote data ...',
        'Restore_CreateFileList': 'Building list of files to restore ...',
        'Restore_CreateTargetFolders': 'Creating target folders ...',
        'Restore_ScanForExistingFiles': 'Scanning existing files ...',
        'Restore_ScanForLocalBlocks': 'Scanning for local blocks ...',
        'Restore_PatchWithLocalBlocks': 'Patching files with local blocks ...',
        'Restore_DownloadingRemoteFiles': 'Downloading files ...',
        'Restore_PostRestoreVerify': 'Verifying restored files ...',
        'Restore_Complete': 'Finished!',
        'Error': 'Error!'
    };

    PRIVATE_DATA.update_progress_and_schedules = function() {
        var scheduledMap = {};
        var backupMap = {};
        var state = PRIVATE_DATA.server_state;
        var current = state.activeTask == null ? false : 'backup-' + state.activeTask.Item2;

        if (state.activeTask == null) {
            $('#main-status-area-cancel-button').hide();
            $('#main-status-area-progress-outer').hide();

            //TODO: state.scheduled is just the current queue, we need to find the next based on "Time"


            if (PRIVATE_DATA.backup_list == null || PRIVATE_DATA.backup_list.length == 0) {
                $('#main-status-area-text').text('No scheduled backups waiting');
            } else {
                var bk = null;
                var mindate = null;
                for(var n in PRIVATE_DATA.backup_list) {
                    if (PRIVATE_DATA.backup_list[n].Schedule == null || PRIVATE_DATA.backup_list[n].Schedule.Time == null)
                        continue;

                    var selfdate = new Date(PRIVATE_DATA.backup_list[n].Schedule.Time);
                    if (mindate == null || selfdate < mindate) {
                        bk = PRIVATE_DATA.backup_list[n].Backup;
                        mindate = selfdate;
                    }
                }

                if (bk == null) {
                    $('#main-status-area-text').text('No scheduled backups waiting');
                } else {
                    $('#main-status-area-text').text('Waiting to start "' + bk.Name + '" at ' + $.toDisplayDateAndTime(mindate));
                }
            }
        } else {
            $('#main-status-area-cancel-button').show();
            $('#main-status-area-progress-outer').show();

            var id = state.activeTask.Item2;
            var bk = null;

            for(var n in PRIVATE_DATA.backup_list)
                if (PRIVATE_DATA.backup_list[n].Backup.ID == id) {
                    bk = PRIVATE_DATA.backup_list[n].Backup;
                    break;
                }

            if (bk == null)
                $('#main-status-area-text').text('Unknown backup');
            else
                $('#main-status-area-text').text(bk.Name);

            var txt = 'Running ...';
            var pg = -1;
            if (PRIVATE_DATA.server_progress.lastEvent != null) {

                if (PRIVATE_DATA.server_progress.lastEvent.Phase == 'Backup_ProcessingFiles') {

                    if (PRIVATE_DATA.server_progress.lastEvent.StillCounting) {
                        txt = 'Counting (' + PRIVATE_DATA.server_progress.lastEvent.TotalFileCount + ' files found, ' + $.formatSizeString(PRIVATE_DATA.server_progress.lastEvent.TotalFileSize) + ')';
                        pg = 0;
                    } else {
                        var filesleft = PRIVATE_DATA.server_progress.lastEvent.TotalFileCount - PRIVATE_DATA.server_progress.lastEvent. ProcessedFileCount;
                        var sizeleft = PRIVATE_DATA.server_progress.lastEvent.TotalFileSize - PRIVATE_DATA.server_progress.lastEvent.ProcessedFileSize;
                        pg = PRIVATE_DATA.server_progress.lastEvent.ProcessedFileSize / PRIVATE_DATA.server_progress.lastEvent.TotalFileSize;

                        if (PRIVATE_DATA.server_progress.lastEvent.ProcessedFileCount == 0)
                            pg = 0;
                        else if (pg == 1)
                            pg = 0.95;

                        txt = filesleft + ' files (' + $.formatSizeString(sizeleft) + ') to go';
                    }
                } else {
                    txt = PRIVATE_DATA.progress_state_text[PRIVATE_DATA.server_progress.lastEvent.Phase] || txt;
                    if (PRIVATE_DATA.server_progress.lastEvent.Phase == 'Backup_WaitForUpload')
                        pg = 1;

                }

                if (pg == -1) {
                    $('#main-status-area-progress-outer').addClass('backup-progress-indeterminate');
                    $('#main-status-area-progress-bar').css('width', '0%');
                } else {
                    $('#main-status-area-progress-outer').removeClass('backup-progress-indeterminate');
                    $('#main-status-area-progress-bar').css('width', parseInt(pg*100) + '%');
                }
                $('#main-status-area-progress-text').text(txt);

            }
        }

        if (state.scheduled != null)
            for(var n in state.scheduled)
                if (scheduledMap['backup-' + state.scheduled[n].Item2] == null)
                    scheduledMap['backup-' + state.scheduled[n].Item2] = parseInt(n) + 1;

        for(var n in PRIVATE_DATA.backup_list)
            backupMap['backup-' + PRIVATE_DATA.backup_list[n].Backup.ID] = PRIVATE_DATA.backup_list[n];

        $('#main-list').find('.main-backup-entry').each(function(i, e) {
            var el = $(e);

            if (isNaN($.parseDate(backupMap[e.id].Backup.Metadata.LastBackupStarted))) {
                el.find('.last-run-time').hide();
            } else {
                el.find('.last-run-time').show();
            }

            // Scheduled items
            if (scheduledMap[e.id]) {
                el.find('.backup-next-run').text('#' + parseInt(scheduledMap[e.id]) + ' in queue');
                el.find('.next-run-time').show();
            } else if (e.id == current) {
                el.find('.backup-next-run').text('Running now');
                el.find('.next-run-time').show();
            } else if (backupMap[e.id] && backupMap[e.id].Schedule) {
                var targetDate = $.parseDate(backupMap[e.id].Schedule.Time);
                el.find('.backup-next-run').text($.toDisplayDateAndTime(targetDate));
                el.find('.next-run-time').show();
            } else {
                el.find('.next-run-time').hide();
            }
        });
    };

    PRIVATE_DATA.long_poll_for_status = function() {
        var state = PRIVATE_DATA.server_state;
        if (state.polling)
            return;

        if (state.retryTimer != null) {
            $(document).trigger('server-state-connecting');
            clearInterval(state.retryTimer);
            state.retryTimer = null;
        }

        state.polling = true;

        var req = { action: 'get-current-state', longpoll: true, lastEventId: state.eventId };
        var timeout = 240000;
        if (state.failed || state.eventId == -1) {
            req.longpoll = false;
            timeout = 5000
        }

        req.duration = parseInt((timeout-1000) / 1000) + 's';

        $.ajax({
            url: APP_CONFIG.server_url,
            type: 'GET',
            timeout: timeout,
            dataType: 'json',
            data: req
        })
        .done(function(data) {
            state.polling = false;
            state.state = data;
            var oldDataId = state.dataEventId;
            var oldState = state.programState;
            var oldEventId = state.eventId;

            state.eventId = data.LastEventID;
            state.dataEventId = data.LastDataUpdateID;
            state.programState = data.ProgramState;
            state.scheduled = data.SchedulerQueueIds || [];

            if (state.failed) {
                state.failed = false;

                // If the server was restarted, we refresh
                if (oldEventId > state.eventId)
                    location.reload(true);

                $(document).trigger('server-state-restored');
            }

            //If there is an active backup, (re)start the progress monitor
            if (data.ActiveTask != null) {
                state.activeTask = data.ActiveTask;
                PRIVATE_DATA.poll_for_progress();
            } else {
                state.activeTask = null;
            }

            $(document).trigger('server-state-updated', data);
            if (oldDataId != state.dataEventId)
                $(document).trigger('server-state-data-updated', data);

            if (oldState != state.programState) {
                if (state.pauseUpdateTimer != null) {
                    clearInterval(state.pauseUpdateTimer);
                    state.pauseUpdateTimer = null;
                }

                $(document).trigger('server-state-changed', state.programState);

                if (state.programState == 'Running') {
                    $(document).trigger('server-state-running');
                } else if (state.programState == 'Paused') {
                    $(document).trigger('server-state-paused');

                    var estimatedEnd = Date.parse(data.EstimatedPauseEnd);
                    if (estimatedEnd > 0) {
                        estimatedEnd = new Date(estimatedEnd);
                        var updateTimer = function() {
                            var left = Math.max(0, estimatedEnd - new Date());
                            $(document).trigger('server-state-pause-countdown', {millisecondsLeft: left});
                            if (left == 0) {
                                clearInterval(state.pauseUpdateTimer);
                                state.pauseUpdateTimer = null;
                            }

                        }

                        state.pauseUpdateTimer = setInterval(updateTimer, 500);
                        updateTimer();
                    }
                }
            }

            PRIVATE_DATA.update_progress_and_schedules();
            PRIVATE_DATA.long_poll_for_status();
        })
        .fail(function(data) {
            if (data.status == 401)
                window.location = '/login.html';

            state.polling = false;
            if (!state.failed) {
                state.failed = true;
                $(document).trigger('server-state-lost');
            }

            state.retryStartTime = new Date();
            var updateTimer = function() {
                var n = new Date() - state.retryStartTime;
                var left = Math.max(0, 15000 - n);
                $(document).trigger('server-state-countdown', {millisecondsLeft: left});
                if (left <= 0)
                    PRIVATE_DATA.long_poll_for_status();
            }

            state.retryTimer = setInterval(updateTimer, 500);
        });
    };

    PRIVATE_DATA.server_progress = {
        polling: false,
        eventId: -1,
        throttleTimer: null,
        lastEvent: null,
        updateFreq: 2000
    };

    PRIVATE_DATA.poll_for_progress = function() {
        var state = PRIVATE_DATA.server_progress;
        if (state.polling)
            return;

        if (state.throttleTimer != null) {
            clearTimeout(state.throttleTimer);
            state.throttleTimer = null;
        }

        state.polling = true;

        var req = { action: 'get-progress-state', longpoll: false };
        state.requestStart = new Date();

        var restart_poll = function() {
            if (state.throttleTimer != null) {
                clearTimeout(state.throttleTimer);
                state.throttleTimer = null;
            }

            var timeSinceLast = new Date() - state.requestStart;
            if (timeSinceLast < state.updateFreq) {
                state.throttleTimer = setTimeout(function(){
                    if (PRIVATE_DATA.server_state.activeTask != null)
                        PRIVATE_DATA.poll_for_progress();

                }, Math.max(500, state.updateFreq - timeSinceLast));
            } else {
                if (PRIVATE_DATA.server_state.activeTask != null)
                    PRIVATE_DATA.poll_for_progress();
            }
        };

        $.ajax({
            url: APP_CONFIG.server_url,
            type: 'GET',
            timeout: 5000,
            dataType: 'json',
            data: req
        })
        .done(function(data) {
            state.polling = false;
            state.eventId = data.LastEventID;
            state.lastEvent = data;
            PRIVATE_DATA.update_progress_and_schedules();

            $(document).trigger('server-progress-updated', data);

            restart_poll();
        })
        .fail(function(data, a, b, c, d) {
            state.polling = false;
            if (PRIVATE_DATA.server_state.failed) {
                // We will be restarted on re-connect
            } else {
                // Bad poll request, just retry
                restart_poll();
            }
        });

    };

    PRIVATE_DATA.refresh_server_settings = function(callback, errorhandler) {
        serverWithCallback(
            'system-info',
            callback,
            errorhandler,
            function(data, success) {
                if (success && data != null) {
                    PRIVATE_DATA.server_config = data;
                    window.document.title = APP_CONFIG.branded_name + ' - ' + data.MachineName;
                }
            }
        );
    };

    PRIVATE_DATA.refresh_backup_list = function(callback, errorhandler) {
        serverWithCallback(
            'list-backups',
            callback,
            errorhandler,
            function(data, success) {
                if (success && data != null) {
                    PRIVATE_DATA.backup_list = data;

                    // Clear existing stuff
                    $('#main-list-container > div.main-backup-entry').remove();

                    if ($('#backup-item-template').length > 0 && data.length > 0) {

                        // Pre-processing of data
                        for(var n in data)
                            data[n].Backup.Metadata = data[n].Backup.Metadata || {};

                        if (APP_DATA.plugins.primary['backup-list-preprocess'])
                            APP_DATA.plugins.primary['backup-list-preprocess'](data);

                        //Fill with jQuery template
                        $.tmpl($('#backup-item-template'), data).prependTo($('#main-list-container'));

                        var decodeid = function(e) {
                            var p = e.delegateTarget.id.split('-');
                            return parseInt(p[p.length - 1]);
                        };

                        // Post processing of data
                        for(var n in data) {
                            var id = data[n].Backup.ID;

                            // Setup context menu trigger
                            $('#backup-control-' + id).click(function(e) {
                                var id = decodeid(e);
                                APP_DATA.showContextMenu(id, $('#backup-control-' + id));
                            });
                        }

                        if (APP_DATA.plugins.primary['backup-list-postrocess'])
                            APP_DATA.plugins.primary['backup-list-postrocess']($('#main-list-container'), $('#main-list-container > div.main-backup-entry'), data);
                    }
                }

                PRIVATE_DATA.update_progress_and_schedules();
            }
        );
    };

    APP_DATA.getBackupName = function(id) {
        if (PRIVATE_DATA.backup_list)
            for(var n in PRIVATE_DATA.backup_list)
                if (PRIVATE_DATA.backup_list[n].Backup.ID == id)
                    return PRIVATE_DATA.backup_list[n].Backup.Name;
        return null;
    };

    APP_DATA.getServerConfig = function(callback, errorhandler) {
        if (PRIVATE_DATA.server_config == null) {
            PRIVATE_DATA.refresh_server_settings(callback, errorhandler);
        } else {
            callback(PRIVATE_DATA.server_config);
        }
    };

    APP_DATA.validatePath = function(path, callback) {
        serverWithCallback({ action: 'validate-path', path: path }, callback, callback);
    };
    APP_DATA.getLabels = function(callback) {
        serverWithCallback('list-tags', callback, callback);
    };

    APP_DATA.getBackupDefaults = function(callback, errorhandler) {
        serverWithCallback('get-backup-defaults', callback, errorhandler);
    };
    APP_DATA.getBackupData = function(id, callback, errorhandler) {
        serverWithCallback({ action: 'get-backup', id: id}, callback, errorhandler);
    };

    APP_DATA.addBackup = function(cfg, callback, errorhandler) {
        serverWithCallback(
            { action: 'add-backup', HTTP_METHOD: 'POST', data: JSON.stringify(cfg)},
            callback,
            errorhandler
        );
    };

    APP_DATA.updateBackup = function(cfg, callback, errorhandler) {
        serverWithCallback(
            { action: 'update-backup', HTTP_METHOD: 'POST', data: JSON.stringify(cfg)},
            callback,
            errorhandler
        );
    };


    APP_DATA.editNewBackup = function() {
        APP_DATA.editBackup();
    };

    APP_DATA.deleteBackup = function(id, callback, errorhandler) {
        serverWithCallback(
            { action: 'delete-backup', id: id },
            callback,
            errorhandler
        );
    };

    APP_DATA.editBackup = function(id) {
        APP_DATA.getServerConfig(function(data) {

            var callback = function(data) {
                $("#edit-dialog").dialog('open');

                // Bug-fix, this will remove style="width: auto", which breaks Chrome a bit
                $("#edit-dialog").css('width', '');

                // Send data to the dialog
                $("#edit-dialog").trigger('setup-dialog', data.data);
            };

            var errorhandler = function() {
                alert('Failed to get server setup...')
            };

            if (id == undefined || parseInt(id) < 0)
                APP_DATA.getBackupDefaults(callback, errorhandler);
            else
                APP_DATA.getBackupData(id, callback, errorhandler);
        },
        function() {
            alert('Failed to get server setup...')
        });

    };

    APP_DATA.runBackup = function(id) {
        serverWithCallback(
            { action: 'send-command', command: 'run-backup', id: id },
            function() {},
            function(d,s,m) { alert('Failed to start backup: ' + m); }
        );
    };

    APP_DATA.isBackupActive = function(id, callback) {
        serverWithCallback(
            { action: 'send-command', command: 'is-backup-active', id: id },
            function(data) { callback(data.Active); },
            function(d,s,m) { alert('Failed to query backup: ' + m); }
        );
    };

    APP_DATA.restoreBackup = function(id) {
        APP_DATA.isBackupActive(id, function(active) {
            if (active) {
                alert('Cannot start restore while the backup is active.');
            } else {
                $('#restore-dialog').dialog('open');
                $('#restore-dialog').trigger('setup-data', id);
            }
        });

    };

    APP_DATA.pauseServer = function(duration) {
        serverWithCallback({action: 'send-command', command: 'pause', duration: duration});
    };

    APP_DATA.resumeServer = function() {
        serverWithCallback({action: 'send-command', command: 'resume'});
    };

    APP_DATA.stopTask = function(taskId, force) {
        serverWithCallback(
            { action: 'send-command', command: force ? 'abort' : 'stop', taskid: taskId },
            function() {},
            function(d,s,m) { alert('Failed to send stop command: ' + m); }
        );
    }

    APP_DATA.runVerify = function(id) {
        serverWithCallback(
            { action: 'send-command', command: 'run-verify', id: id },
            function() {},
            function(d,s,m) { alert('Failed to start verification: ' + m); }
        );
    };

    APP_DATA.runRepair = function(id) {
        serverWithCallback(
            { action: 'send-command', command: 'run-repair', id: id },
            function() {},
            function(d,s,m) { alert('Failed to start repair: ' + m); }
        );
    };

    APP_DATA.createReport = function(id) {
        serverWithCallback(
            { action: 'send-command', command: 'create-report', id: id },
            function() {},
            function(d,s,m) { alert('Failed to create bug report: ' + m); }
        );
    };

    APP_DATA.hasLoadedAbout = false;

    APP_DATA.showAbout = function() {

        var licenses = {
            'MIT': 'http://www.linfo.org/mitlicense.html',
            'Apache': 'https://www.apache.org/licenses/LICENSE-2.0.html',
            'Apache 2': 'https://www.apache.org/licenses/LICENSE-2.0.html',
            'Apache 2.0': 'https://www.apache.org/licenses/LICENSE-2.0.html',
            'Public Domain': 'https://creativecommons.org/licenses/publicdomain/',
            'GPL': 'https://www.gnu.org/copyleft/gpl.html',
            'LGPL': 'https://www.gnu.org/copyleft/lgpl.html',
            'MS-PL': 'http://opensource.org/licenses/MS-PL',
            'Microsoft Public': 'http://opensource.org/licenses/MS-PL',
            'New BSD': 'http://opensource.org/licenses/BSD-3-Clause'
        };

        if (!APP_DATA.hasLoadedAbout) {
            serverWithCallback('get-license-data',
                function(data) {
                    var d = [];
                    for(var n in data) {
                        try {
                            var r = JSON.parse(data[n].Jsondata);
                            if (r != null) {
                                r.licenselink = r.licenselink || licenses[r.license] || '#';
                                d.push(r);
                            }
                        } catch (e) {}
                    }

                    $('#about-dialog-current-version').text(PRIVATE_DATA.server_config.ServerVersionName);

                    $('#about-dialog-thirdparty-list').empty();
                    $('#about-dialog-thirdparty-list').append($.tmpl($('#about-dialog-template'), d));
                    APP_DATA.hasLoadedAbout = true;
                    APP_DATA.showAbout();
                }, function(msg) {
                    alert('Failed to load data: ' + msg);
                });
        } else {
            $('#about-dialog').dialog('open');
        }
    };

    APP_DATA.checkForUpdates = function() {
        serverWithCallback(
            { action: 'send-command', command: 'check-update' },
            function() {},
            function(d,s,m) { alert('Failed to check for updates: ' + m); }
        );
    };

    APP_DATA.installUpdate = function() {
        serverWithCallback(
            { action: 'send-command', command: 'install-update' },
            function() {},
            function(d,s,m) { alert('Failed to install for update: ' + m); }
        );
    };

    APP_DATA.activateUpdate = function() {
        serverWithCallback(
            { action: 'send-command', command: 'activate-update' },
            function() {},
            function(d,s,m) { alert('Failed to activate update: ' + m); }
        );
    };


    APP_DATA.callServer = serverWithCallback;

    $.showPopupMenu = function(menu, anchor, offset) {
        var pos = anchor.offset();
        var menuwidth = menu.outerWidth();
        var menuheight = menu.outerHeight();

        offset = offset || {};
        offset.x = offset.x || 0;
        offset.y = offset.y || 0;

        menu.toggle();

        var left = pos.left + offset.x;
        var top = pos.top + offset.y;
        if (left + menuwidth > $(document).outerWidth())
            left -= menuwidth;
        if (top + menuheight > $(document).outerHeight())
            top -= menuheight;

        if (menu.is(':visible')) {
            menu.css({
                position: 'absolute',
                top: top + 'px',
                left: left + 'px'
            });
            $('#click-intercept').show();
        }
    };

    $('#main-settings').click(function() {
        var pos = $('#main-settings').offset();
        var buttonwidth = $('#main-settings').outerWidth();
        $.showPopupMenu($('#main-control-menu'), $('#main-settings'), {y: $('#main-topbar').outerHeight() - pos.top });

        $('#main-control-menu').css({ left: "", right: ($(document).outerWidth() - (pos.left + buttonwidth)) + 'px' });
    });

    $('#click-intercept').click(function() {
        $('#click-intercept').hide();
        $('.menu').hide();
    });

    $('#main-donate').click(function() {

    });

    $('#main-control').click(function() {
    });

    $('#main-newbackup').click(function() {
        APP_DATA.editNewBackup();

    });

    $('#edit-dialog').tabs({ active: 0 });
    $('#edit-dialog').dialog({
        minWidth: 320,
        width: $('body').width > 600 ? 320 : 600,
        minHeight: 480,
        height: 500,
        modal: true,
        autoOpen: false,
        closeOnEscape: true,
        buttons: [
            { text: '< Previous', disabled: true, click: function(event, ui) {
                var cur = parseInt($('#edit-dialog').tabs( "option", "active"));
                cur = Math.max(cur-1, 0);
                $('#edit-dialog').tabs( "option", "active", cur);
            }},
            { text: 'Next >', click: function(event, ui) {
                var cur = parseInt($('#edit-dialog').tabs( "option", "active"));
                var max = $('#edit-dialog').parent().find('[role=tablist] > li').size() - 1;

                if (!EDIT_BACKUP.validate_tab(cur))
                    return;

                event.curPage = cur;
                event.currentTarget.curPage = cur;
                cur = Math.min(cur+1, max);
                $('#edit-dialog').tabs( "option", "active", cur);
            }}
        ]
    });

    $(document).on('server-state-updated', function() {
        $('#loading-dialog').dialog("close");
    });

    $(document).on('server-state-lost', function() {
        $('#connection-lost-dialog').dialog('open');
        $('#connection-lost-dialog-text').text('Server connection lost...');
        var button = $('#connection-lost-dialog').parent().find('.ui-dialog-buttonpane').find('.ui-button').first();
        button.attr("disabled",false).removeClass( 'ui-button-disabled ui-state-disabled' );

    });

    $(document).on('server-state-restored', function() {
        $('#connection-lost-dialog').dialog('close');
    });

    $(document).on('server-state-countdown', function(e, f) {
        var s = parseInt(f.millisecondsLeft / 1000) + 1;
        $('#connection-lost-dialog-text').html('Server connection lost, <br/>retry in ' + s + ' seconds');
        var button = $('#connection-lost-dialog').parent().find('.ui-dialog-buttonpane').find('.ui-button').first();
        button.attr("disabled",false).removeClass( 'ui-button-disabled ui-state-disabled' );
    });

    $(document).on('server-state-connecting', function(e) {
         $('#connection-lost-dialog-text').text('Retrying...');
        var button = $('#connection-lost-dialog').parent().find('.ui-dialog-buttonpane').find('.ui-button').first();
        button.attr("disabled",true).addClass( 'ui-button-disabled ui-state-disabled' );
    });

    $(document).on('server-state-running', function() {
        $('#main-control').removeClass('main-icon-run').addClass('main-icon-pause');
    });

    $(document).on('server-state-paused', function() {
        $('#main-control').removeClass('main-icon-pause').addClass('main-icon-run');
    });

    $(document).on('server-state-pause-countdown', function(e, f) {
        var s = parseInt(f.millisecondsLeft / 1000) + 1;
    });

    $('#main-control').click(function() {
        if ($('#main-control').hasClass('main-icon-run'))
            APP_DATA.resumeServer();
        else
            APP_DATA.pauseServer();
    });

    $('#connection-lost-dialog').dialog({
        modal: true,
        autoOpen: false,
        closeOnEscape: false,
        buttons: [
            {
                text: 'Retry now',
                disabled: true,
                click: function(event, ui) {
                    PRIVATE_DATA.long_poll_for_status();
                }
            }
        ]
    });

    $('#connection-lost-dialog').parent().find('.ui-dialog-titlebar-close').remove().first().remove();

    $('#connection-lost-dialog').on('dialogbeforeclose', function( event, ui ) {
        return !PRIVATE_DATA.server_state.failed;
    });


    $(document).on('server-state-data-updated', function() {
        PRIVATE_DATA.refresh_backup_list();
    });

    PRIVATE_DATA.long_poll_for_status();
    PRIVATE_DATA.refresh_server_settings();
    PRIVATE_DATA.refresh_backup_list();

    $('#main-control-menu').menu();
    $('#main-control-menu').removeClass('ui-widget-content');
    $('#main-control-menu-pause-submenu').removeClass('ui-widget-content');
    $('#main-control-menu-settings').hide().next().hide();
    $('#main-control-menu-throttle').hide();

    $('#main-control-menu-about').click(function() { APP_DATA.showAbout(); })

    $('#main-control-menu-pause-submenu-5m').click(function() { APP_DATA.pauseServer('5m'); });
    $('#main-control-menu-pause-submenu-10m').click(function() { APP_DATA.pauseServer('10m'); });
    $('#main-control-menu-pause-submenu-15m').click(function() { APP_DATA.pauseServer('15m'); });
    $('#main-control-menu-pause-submenu-30m').click(function() { APP_DATA.pauseServer('30m'); });
    $('#main-control-menu-pause-submenu-1h').click(function() { APP_DATA.pauseServer('1h'); });

    var updaterState = {
        state: 'Waiting',
        version: null,
        installed: false,
        restarted: false,
        simplestate: null,
        noty: null,
        noty_type: null,

        closeNoty: function() {
            if (this.noty != null) {
                this.noty.close();
                this.noty = null;
                this.noty_type = null;
            }
        },
        checkingNoty: function() {
            this.closeNoty();
            this.noty = noty({
                text: 'Checking for updates ...',
            });
            this.noty_type = 'checking';
        },
        downloadingNoty: function() {
            this.closeNoty();
            var self = this;
            this.noty = noty({
                text: 'Downloading update ' + self.version,
            });
            this.noty_type = 'downloading';
        },
        setDownloadProgress: function(pg) {
            if (this.noty_type == 'downloading' && this.noty != null)
                this.noty.setText('Downloading update ' + this.version + ' (' + parseInt(pg*100) + '%)');

        },
        foundupdateNoty: function() {
            this.closeNoty();
            var self = this;
            this.noty = noty({
                text: 'Found update ' + self.version,
                buttons: [{
                    text: 'Not now',
                    onClick: function() {
                        self.closeNoty();
                    }
                },{
                    text: 'Install',
                    onClick: function() {
                        APP_DATA.installUpdate();
                    }
                }]
            });
            this.noty_type = 'found';
        },
        updateinstalledNoty: function() {
            this.closeNoty();
            var self = this;
            this.noty = noty({
                text: 'Update ' + self.version + ' installed',
                buttons: [{
                    text: 'Not now',
                    onClick: function() {
                        self.closeNoty();
                    }
                },{
                    text: 'Restart',
                    onClick: function() {
                        self.activateUpdate();
                        self.closeNoty();
                    }
                }]
            });
            this.noty_type = 'installed';
        },
        activateUpdate: function() {
            if (confirm('Restart ' + APP_CONFIG.branded_name + ' and activate update?'))
            {
                APP_DATA.activateUpdate();
                updaterState.restarted = true;
            }
        },
        noUpdatesNoty: function() {
            noty({
                text: 'No updates found',
                timeout: 5000
            });
        }
    };

    $(document).on('server-state-updated', function(eventargs, data) {
        var prevstate = updaterState.simplestate;

        updaterState.state = data.UpdaterState;
        updaterState.version = data.UpdatedVersion;
        updaterState.installed = data.UpdateReady;

        if (updaterState.restarted && updaterState.version == null)
            location.reload(true);

        if (updaterState.state == 'Waiting') {
            if (updaterState.version == null)
                updaterState.simplestate = null;
            else if (!updaterState.installed) 
                updaterState.simplestate = 'found';
            else if (updaterState.installed)
                updaterState.simplestate = 'installed';
            else
                updaterState.simplestate = null;

        } else if (updaterState.state == 'Checking') {
            updaterState.simplestate = 'check';
        } else if (updaterState.state == 'Downloading') {
            updaterState.simplestate = 'download';
        } else {
            updaterState.simplestate = null;
        }

        if (updaterState.simplestate == null) {
            $('#main-control-menu-updates > a').text('Check for updates');
            $('#main-control-menu-updates').removeClass('ui-state-disabled');
            $('#main-control-menu-check-updates').hide();
        } else if (updaterState.simplestate == 'found') {
            $('#main-control-menu-updates > a').text('Install update');
            $('#main-control-menu-updates').removeClass('ui-state-disabled');
            $('#main-control-menu-check-updates').show();
        } else if (updaterState.simplestate == 'installed') {
            $('#main-control-menu-updates > a').text('Restart with update');
            $('#main-control-menu-updates').removeClass('ui-state-disabled');
            $('#main-control-menu-check-updates').hide();
        } else if (updaterState.simplestate == 'check') {
            $('#main-control-menu-updates > a').text('Checking for update ...');
            $('#main-control-menu-updates').addClass('ui-state-disabled');
            $('#main-control-menu-check-updates').hide();
        } else if (updaterState.simplestate == 'download') {
            $('#main-control-menu-updates > a').text('Downloading update ...');
            $('#main-control-menu-updates').addClass('ui-state-disabled');
            $('#main-control-menu-check-updates').hide();
        } else {
            $('#main-control-menu-updates > a').text('Unknown state ...');
            $('#main-control-menu-updates').addClass('ui-state-disabled');
            $('#main-control-menu-check-updates').show();
        }

        if (updaterState.simplestate != prevstate) {
            if (updaterState.simplestate == 'found')
                updaterState.foundupdateNoty();
            else if (updaterState.simplestate == 'installed')
                updaterState.updateinstalledNoty();                
            else if (updaterState.simplestate == 'check')
                updaterState.checkingNoty();                
            else if (updaterState.simplestate == 'download')
                updaterState.downloadingNoty();
            else
            {
                updaterState.closeNoty();
                if (prevstate == 'check')
                    updaterState.noUpdatesNoty();
            }
        } else if (updaterState.simplestate == 'download') {
            updaterState.setDownloadProgress(data.UpdateDownloadProgress);
        }

    });

    $('#main-control-menu-check-updates').click(function() {
        if (updaterState.state != 'Waiting')
            return;

        APP_DATA.checkForUpdates();
    });

    $('#main-control-menu-updates').click(function() {
        if (updaterState.state != 'Waiting')
            return;

        if (updaterState.version == null) {
            APP_DATA.checkForUpdates();
        } else if (!updaterState.installed) {
            APP_DATA.checkForUpdates();
            APP_DATA.installUpdate();
        } else if (updaterState.installed) {
            updaterState.activateUpdate();
        }
    });

    $('#main-control-menu-log').click(function() { $.showAppLog(); });

    $('#main-control-menu').find('li').click(function() {  $('#click-intercept').trigger('click'); });

    var pausenoty = {
        n: null,
        prevstate: null
    };

    $(document).on('server-state-changed', function(e, state) {
        if (pausenoty.prevstate != state && pausenoty.prevstate != null) {
            if (pausenoty.n != null)
                pausenoty.n.close();

            if (state == 'Running') {
                pausenoty.n = noty({
                    text: 'Server is now resumed',
                    timeout: 5000
                });
            } else {
                pausenoty.n = noty({
                    text: 'Server is paused',
                    buttons: [{
                        text: 'Resume',
                        onClick: function() {
                            APP_DATA.resumeServer();
                        }
                    }]
                });
            }
        }

        pausenoty.prevstate = state;
    });

    $(document).on('server-state-pause-countdown', function(e, data) {
        if (pausenoty.prevstate != 'Paused' || pausenoty.n == null) {
            if (pausenoty.n != null)
                pausenoty.n.close();
            pausenoty.n = noty({
                text: 'Server is paused',
                buttons: [{
                    text: 'Resume',
                    onClick: function() {
                        APP_DATA.resumeServer();
                    }
                }]
            });
        }
        var seconds = parseInt(data.millisecondsLeft / 1000);
        var hours = parseInt(seconds / 3600);
        seconds -= (hours * 3600);
        var minutes = parseInt(seconds / 60)
        seconds -= (minutes * 60);
        seconds = seconds + '';
        if (seconds.length == 1)
            seconds = '0' + seconds;

        var timestr = '';
        if (hours > 0)
            timestr += hours + 'h ';

        timestr += minutes + ':' + seconds;

        pausenoty.n.setText('Server is paused, resuming in ' + timestr);
    });

    $("#about-dialog").dialog({
        minWidth: 320,
        width: $('body').width > 600 ? 320 : 600,
        minHeight: 480,
        height: 500,
        modal: true,
        autoOpen: false,
        closeOnEscape: true,
        buttons: [
            { text: 'Close', disabled: false, click: function(event, ui) {
                $('#about-dialog').dialog('close');
            }}
        ]
    });

    setInterval(function() {
        $('#main-list').find('.backup-last-run').each(function(i, e) {
          $(e).text($.timeago($.parseDate($(e).attr('alt'))));
        })
    }, 60*1000);

    APP_DATA.getCurrentBackupId = function() {
        return PRIVATE_DATA.server_state.activeTask == null ? null : PRIVATE_DATA.server_state.activeTask.Item2;
    };

    APP_DATA.getCurrentTaskId = function() {
        return PRIVATE_DATA.server_state.activeTask == null ? null : PRIVATE_DATA.server_state.activeTask.Item1;
    };

    APP_DATA.contextMenuId = null;
    APP_DATA.showContextMenu = function(id, anchor) {
        APP_DATA.contextMenuId = id;
        $.showPopupMenu($('#backup-context-menu'), anchor, {x: $(anchor).outerWidth() + 10});
    };

    $('#backup-details-run').click(function(e) {
        APP_DATA.runBackup(APP_DATA.contextMenuId);
    });

    $('#backup-details-restore').click(function(e) {
        APP_DATA.restoreBackup(APP_DATA.contextMenuId);
    });

    $('#backup-details-edit').click(function(e) {
        APP_DATA.editBackup(APP_DATA.contextMenuId);
    });

    $('#backup-details-delete').click(function(e) {
        var name = APP_DATA.getBackupName(APP_DATA.contextMenuId);

        if (name && confirm('Really delete ' + name + '?'))
            APP_DATA.deleteBackup(APP_DATA.contextMenuId);
    });

    $('#backup-details-show-log').click(function(e) {
        $.showBackupLog(APP_DATA.contextMenuId);
    });

    $('#backup-details-verify').click(function(e) {
        APP_DATA.runVerify(APP_DATA.contextMenuId);
    });

    $('#backup-details-send-report').click(function(e) {
        alert('Method is missing a method for retrieving the report after it has been generated');
        APP_DATA.createReport(APP_DATA.contextMenuId);
    });

    $('#backup-details-repair').click(function(e) {
        APP_DATA.runRepair(APP_DATA.contextMenuId);
    });

    $('#backup-details-copy').click(function(e) {
        alert('Function is not implemented yet');
    });

    $('#backup-details-delete-local').click(function(e) {
        alert('Function is not implemented yet');
    });

    $('#backup-details-delete-remote').click(function(e) {
        alert('Function is not implemented yet');
    });

    $('#backup-context-menu').menu().removeClass('ui-widget-content');
    $('#backup-context-menu').find('li').click(function() {  $('#click-intercept').trigger('click'); });


    var cancelnoty = {
        n: null,
        taskId: null,
        onRequest: function() {
            var current = APP_DATA.getCurrentTaskId();

            if (current == null)
                return;

            // Seems too harsh to use the double-stop = force logic
            /*if (this.cancel_task_id == current) {
                APP_DATA.stopTask(current, true);
            } else*/ {
                APP_DATA.stopTask(current, false);
                if (this.cancel_task_id != current) {
                    this.cancel_task_id = current;

                    if (this.n != null) {
                        this.n.close();
                        this.n = null;
                    }

                    this.n = noty({
                            text: 'Task stopped, waiting for task to clean up',
                            buttons: [{
                                text: 'Force stop',
                                onClick: function() {
                                    APP_DATA.stopTask(current, true);
                                }
                            }]
                        });
                }
            }
        },
        onUpdate: function() {
            if (this.n == null)
                return;

            var current = APP_DATA.getCurrentTaskId();
            if (current != this.cancel_task_id) {
                this.n.close();
                this.n = null;
                this.cancel_task_id = null;
            }

        }
    };


    $(document).on('server-state-updated', function(e, data) {
        cancelnoty.onUpdate();
    });

    $(document).on('server-progress-updated', function(e, data) {
        cancelnoty.onUpdate();
    });

    $('#main-status-area-cancel-button').click(function() {
        var dlg = $('<div></div>').attr('title', 'Warning');
        dlg.dialog({
            autoOpen: true,
            width: $('body').width > 450 ? 430 : 600,
             modal: true,
            closeOnEscape: true,
            buttons: [
                { text: 'Stop now', click: function(event, ui) {
                    var current = APP_DATA.getCurrentTaskId();
                    if (current != null)
                        APP_DATA.stopTask(current, true);

                    dlg.dialog('close');
                    dlg.remove();
                }},
                { text: 'Stop after upload', click: function(event, ui) {
                    cancelnoty.onRequest();
                    dlg.dialog('close');
                    dlg.remove();
                }},
                { text: 'Cancel', click: function(event, ui) {
                    dlg.dialog('close');
                    dlg.remove();
                }}
            ]
        });

        var pgtxt = $('<div></div>');
        pgtxt.text('Stop the current task?');
        dlg.append(pgtxt);

    });

});